using System;
using System.Linq;
using System.Collections.Generic;

namespace ContinualFlame.Ajax
{	
	public class SynchronizationManager
	{
		object _lock = new object();
		
		HashSet<SynchronizedObject> _broadcastObjects = 
			new HashSet<SynchronizedObject>();
		
		Dictionary<SynchronizedObject, string> _unicastObjects = 
			new Dictionary<SynchronizedObject, string>();
		
		Dictionary<string, ClientConnection> _activeConnections = 
			new Dictionary<string, ClientConnection>();
		
		// register a synchronized object that is visible to all connected users
		public void RegisterBroadcastSource(SynchronizedObject src)
		{
			lock (_lock)
			{
				src.Owner = this;
				_broadcastObjects.Add(src);
			}
		}
		
		// register a synchronized object for a single user (private chat channels, etc)
		public void RegisterSource(string clientId, SynchronizedObject src)
		{
			lock (_lock)
			{
				src.Owner = this;
				_unicastObjects.Add(src, clientId);
			}
		}
		
		// register a recently connected client
		public void RegisterClient(long watermark, string clientId, ClientConnection conn)
		{
			lock (_lock)
			{
				//
				// add client to queue
				//
				
				_activeConnections[clientId] = conn;
				
				//
				// sync up any missed messages
				//
				
				var broadcastMessages = _broadcastObjects.SelectMany(x => x.GetDelta(watermark));
				
				var unicastMessages = _unicastObjects
					.Where(x => x.Value == clientId)
					.SelectMany(y => y.Key.GetDelta(watermark));
				
				var allMessages = broadcastMessages.Union(unicastMessages).OrderBy(x => x.Watermark);
				
				foreach (Message m in allMessages)
				{
					conn.SendMessage(m);
				}
			}
		}
		
		// unregister a disconnected client
		public void UnregisterClient(string clientId)
		{
			lock (_lock)
			{
				// ignore return value and unconditionally attempt to remove entry
				_activeConnections.Remove(clientId);
			}
		}
		
		// allows a message source to advise the manager of new content
		public void Deliver(SynchronizedObject src, Message msg)
		{
			lock (_lock)
			{
				if (_broadcastObjects.Contains(src))
				{
					foreach(ClientConnection c in _activeConnections.Values)
					{
						c.SendMessage(msg);
					}	
				}
				else
				{
					string clientId;
					ClientConnection c;
					if (_unicastObjects.TryGetValue(src, out clientId))
					{
						if (_activeConnections.TryGetValue(clientId, out c))
						{
							c.SendMessage(msg);
						}
					}
				}
			}
		}
	}
}
